package com.centerm.bluetooth.ibridge;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Parcel;
import android.os.Parcelable;

import com.centerm.bluetooth.ibridge.constants.BondStatus;
import com.centerm.bluetooth.ibridge.constants.ConnectDirection;
import com.centerm.bluetooth.ibridge.constants.ConnectMode;
import com.centerm.bluetooth.ibridge.constants.ConnectStatus;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;

/**
 * This class represents an iBridge remote Bluetooth device.A
 * BluetoothIBridgeDevice has name, address. It can be constructed from a
 * bluetooth address. This class implements the Parcelable interface, so it can
 * be written to and restored from a Parcel object and can be encapsulated as a
 * parameter into an Intent.
 */
public class BluetoothIBridgeDevice implements Parcelable {
    static final UUID SPPUUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");

    private BluetoothDevice mDevice;
    private String mDeviceAddress;
    private String mDeviceName;
    private boolean mIsConnected = false;
    private ConnectDirection mConnectDirection = ConnectDirection.DIRECTION_NONE;
    private ConnectMode mConnectMode = ConnectMode.MODE_SMART;
    private ConnectStatus mConnectStatus = ConnectStatus.STATUS_DISCONNECTED;
    private BondStatus mBondStatus = BondStatus.STATE_BONDNONE;
    private int mDeviceClass = -1;
    private static boolean tvi = true;

    static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT";
    static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
    static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
    static final String ACTION_PAIRING_CANCEL = "android.bluetooth.device.action.PAIRING_CANCEL";

    static final int PAIRING_VARIANT_PIN = 0;
    static final int PAIRING_VARIANT_PASSKEY = 1;
    static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
    static final int PAIRING_VARIANT_CONSENT = 3;
    static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
    static final int PAIRING_VARIANT_DISPLAY_PIN = 5;
    static final int PAIRING_VARIANT_OOB_CONSENT = 6;

    /* access by BluetoothIBridgeConnManager */
    byte[] buffer;
    int length;

    BluetoothIBridgeDevice(BluetoothDevice device) {
        mDeviceAddress = device.getAddress();
        mDevice = device;
        mDeviceName = mDevice.getName();
        BluetoothClass bluetoothClass = null;
        try {
            bluetoothClass = mDevice.getBluetoothClass();
        } catch (NullPointerException npe) {
        }
        if (bluetoothClass != null) {
            mDeviceClass = mDevice.getBluetoothClass().getDeviceClass();
        } else {
            mDeviceClass = -1;
        }
    }

    /**
     * Call this method to construct a BluetoothIBridgeDevice with the address.
     *
     * @param address the address of a remote device.
     * @return a BluetoothIBridgeDevice with the address.
     */
    public static BluetoothIBridgeDevice createBluetoothIBridgeDevice(String address) {
        BluetoothIBridgeDeviceFactory factory = BluetoothIBridgeDeviceFactory.getDefaultFactory();
        return factory.createDevice(address);
    }

    private BluetoothIBridgeDevice(Parcel source) {
        readFromParcel(source);
    }

    /**
     * Get the Bluetooth name of the remote device.
     *
     * @return the Bluetooth name of the device.
     */
    public String getDeviceName() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        mDevice = adapter.getRemoteDevice(mDeviceAddress);
        mDeviceName = mDevice.getName();
        return mDeviceName;
    }

    /**
     * Get the Bluetooth address of the remote device.
     *
     * @return the Bluetooth address of the device.
     */
    public String getDeviceAddress() {
        return mDeviceAddress;
    }

    boolean isValidDevice() {
        if (tvi)
            return true;
        // return getDeviceAddress().startsWith("00:15:83:") ||
        // getDeviceAddress().startsWith("00:13:8A:");
        return false;
    }

    /**
     * This is a boolean method. Return true if the object
     * BluetoothIBridgeDevice is equal to the current constructed
     * BluetoothIBridgeDevice by address,return false otherwise.
     */
    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }

        if (!(o instanceof BluetoothIBridgeDevice)) {
            return false;
        }

        String addr = ((mDeviceAddress == null) ? "00:00:00:00:00:00" : mDeviceAddress);
        BluetoothIBridgeDevice dev = (BluetoothIBridgeDevice) o;
        String another = ((dev == null || dev.mDeviceAddress == null) ? "00:00:00:00:00:00" : dev.mDeviceAddress);
        return addr.equals(another);
    }

    /**
     * Construct a string with current constructed BluetoothIBridgeDevice name
     * and address.
     *
     * @return a string with BluetoothIBridgeDevice name and address.
     */
    @Override
    public String toString() {
        String name = ((mDeviceName == null) ? "Device" : mDeviceName);
        String addr = ((mDeviceAddress == null) ? "00:00:00:00:00:00" : mDeviceAddress);
        return ((name == "Device") ? addr : name);
    }

    public String toLongString() {
        String name = ((mDeviceName == null) ? "Device" : mDeviceName);
        String addr = ((mDeviceAddress == null) ? "00:00:00:00:00:00" : mDeviceAddress);
        return name + "(" + addr + ")";
    }

    BluetoothSocket createSocket(ConnectMode mode) {
        BluetoothSocket socket = null;
        switch (mode) {
            case MODE_SECURE_FIXED:
                socket = createSecureSocketWithChannel(6);
                break;
            case MODE_INSECURE_FIXED:
                socket = createInsecureSocketWithChannel(6);
                break;
            case MODE_SECURE_NOFIXED:
                socket = createSecureSocket();
                break;
            case MODE_INSECURE_NOFIXED:
                socket = createInsecureSocket();
                break;
            case MODE_SMART:
            case MODE_STRATEGY:
                break;
            default:
                break;
        }
        setConnectMode(mode);
        return socket;
    }

    BluetoothSocket createSecureSocket() {
        BluetoothSocket socket = null;
        try {
            socket = mDevice.createRfcommSocketToServiceRecord(SPPUUID);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return socket;
    }

    BluetoothSocket createInsecureSocket() {
        BluetoothSocket socket = null;
        try {
            socket = mDevice.createInsecureRfcommSocketToServiceRecord(SPPUUID);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return socket;
    }

    BluetoothSocket createSecureSocketWithChannel(int channel) {
        BluetoothSocket socket = null;
        // if (!SystemPropertiesProxy.isMediatekPlatform()) {
        Class<? extends BluetoothDevice> cls = BluetoothDevice.class;
        Method m = null;
        try {
            m = cls.getMethod("createRfcommSocket", int.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        if (m != null) {
            try {
                socket = (BluetoothSocket) m.invoke(mDevice, channel);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        // }
        return socket;
    }

    BluetoothSocket createInsecureSocketWithChannel(int channel) {
        BluetoothSocket socket = null;
        // if (!SystemPropertiesProxy.isMediatekPlatform()) {
        Class<? extends BluetoothDevice> cls = BluetoothDevice.class;
        Method m = null;
        try {
            m = cls.getMethod("createInsecureRfcommSocket", int.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        if (m != null) {
            try {
                socket = (BluetoothSocket) m.invoke(mDevice, channel);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        // }
        return socket;
    }

    void connected(boolean connected) {
        mIsConnected = connected;
    }

    /**
     * Check whether the remote device is connected.
     *
     * @return true if connected, false otherwise.
     */
    public boolean isConnected() {
        return mIsConnected;
    }

    void setConnectDirection(ConnectDirection d) {
        mConnectDirection = d;
    }

    public ConnectDirection getConnectDirection() {
        return mConnectDirection;
    }

    void setConnectStatus(ConnectStatus d) {
        mConnectStatus = d;

    }

    public ConnectStatus getConnectStatus() {
        return mConnectStatus;
    }

    void setBondStatus() {
        if (mDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
            mBondStatus = BondStatus.STATE_BONDED;
        }
        if (mDevice.getBondState() == BluetoothDevice.BOND_BONDING) {
            mBondStatus = BondStatus.STATE_BONDING;
        }
        if (mDevice.getBondState() == BluetoothDevice.BOND_NONE) {
            mBondStatus = BondStatus.STATE_BONDNONE;
        }

    }

    void setBondStatus(BondStatus d) {
        mBondStatus = d;

    }

    public BondStatus getBondStatus() {
        return mBondStatus;
    }

    void setConnectMode(ConnectMode d) {
        mConnectMode = d;
    }

    public ConnectMode getConnectMode() {
        return mConnectMode;
    }

    /**
     * Describe the kinds of special objects contained in this Parcelable's
     * marshalled representation.
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * Flatten this object in to a Parcel.
     *
     * @param dest  the Parcel in which the object should be written.
     * @param flags additional flags about how the object should be written.
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mDeviceName);
        dest.writeString(mDeviceAddress);
        dest.writeInt(mDeviceClass);
        dest.writeInt(mIsConnected ? 1 : 0);
        dest.writeInt(mConnectDirection.ordinal());
        dest.writeInt(mConnectMode.ordinal());
        dest.writeInt(mConnectStatus.ordinal());
        dest.writeInt(mBondStatus.ordinal());
    }

    private void readFromParcel(Parcel source) {
        mDeviceName = source.readString();
        mDeviceAddress = source.readString();
        mDeviceClass = source.readInt();
        mIsConnected = (source.readInt() == 1);
        int i = source.readInt();
        ConnectDirection cd[] = ConnectDirection.values();
        if (i < cd.length) {
            mConnectDirection = ConnectDirection.values()[i];
        } else {
            mConnectDirection = ConnectDirection.DIRECTION_NONE;
        }

        int j = source.readInt();
        ConnectMode cm[] = ConnectMode.values();
        if (j < cm.length) {
            mConnectMode = ConnectMode.values()[j];
        } else {
            mConnectMode = ConnectMode.MODE_SMART;
        }

        int k = source.readInt();
        ConnectStatus cs[] = ConnectStatus.values();
        if (k < cs.length) {
            mConnectStatus = ConnectStatus.values()[k];
        } else {
            mConnectStatus = ConnectStatus.STATUS_DISCONNECTED;
        }

        int l = source.readInt();
        BondStatus bs[] = BondStatus.values();
        if (l < bs.length) {
            mBondStatus = BondStatus.values()[l];
        } else {
            mBondStatus = BondStatus.STATE_BONDNONE;
        }

        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        mDevice = adapter.getRemoteDevice(mDeviceAddress);
        BluetoothIBridgeAdapter.log("readFromParcel:" + mDeviceName);
    }

    public static final Creator<BluetoothIBridgeDevice> CREATOR = new Creator<BluetoothIBridgeDevice>() {

        @Override
        public BluetoothIBridgeDevice createFromParcel(Parcel source) {
            return new BluetoothIBridgeDevice(source);
        }

        @Override
        public BluetoothIBridgeDevice[] newArray(int size) {
            return new BluetoothIBridgeDevice[size];
        }
    };

    /**
     * Create Bond to remote device.
     */
    public void createBond() {
        try {
            mDevice.getClass().getMethod("createBond", (Class[]) null).invoke(mDevice);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * Remove Bond to remote device.
     */
    public void removeBond() {
        try {
            mDevice.getClass().getMethod("removeBond", (Class[]) null).invoke(mDevice);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * Static method to create Bond to remote device.
     */
    public static void createBond(BluetoothDevice device) {
        try {
            device.getClass().getMethod("createBond", (Class[]) null).invoke(device);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * Static method to remove Bond to remote device.
     */
    public static void removeBond(BluetoothDevice device) {
        try {
            BluetoothDevice.class.getMethod("removeBond", (Class[]) null).invoke(device);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    void setPin(String pin) {
        try {
            Class<?> c = Class.forName(mDevice.getClass().getName());
            Method setPin = c.getMethod("setPin", byte[].class);
            setPin.setAccessible(true);
            setPin.invoke(mDevice, new Object[]{pin.getBytes()});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    void setPasskey(int passkey) {
        try {
            Class<?> c = Class.forName(mDevice.getClass().getName());
            Method setPasskey = c.getMethod("setPasskey", int.class);
            setPasskey.setAccessible(true);
            setPasskey.invoke(mDevice, passkey);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    void setPairingConfirmation(boolean confirm) {
        try {
            Class<?> c = Class.forName(mDevice.getClass().getName());
            Method setPairingConfirmation = c.getMethod("setPairingConfirmation", boolean.class);
            setPairingConfirmation.setAccessible(true);
            setPairingConfirmation.invoke(mDevice, confirm);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    void cancelPairingUserInput() {
        try {
            mDevice.getClass().getMethod("cancelPairingUserInput", (Class[]) null).invoke(mDevice);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    boolean isBonded() {
        if (mDevice != null) {
            return mDevice.getBondState() == BluetoothDevice.BOND_BONDED;
        }
        return false;
    }

    boolean isSameDevice(BluetoothDevice device) {
        String addr = ((mDeviceAddress == null) ? "00:00:00:00:00:00" : mDeviceAddress);
        String another = ((device.getAddress() == null) ? "00:00:00:00:00:00" : device.getAddress());
        return addr.equals(another);
    }
}
